use super::{
    err, err_value, json, ok, throw_err, Base, Client, ECode, MapSv, RNull, Resp, TBase, VBase,
    VMap, Value,
};
use std::collections::{HashMap, HashSet};
use tracing::{instrument, warn};

pub struct Work;

type HS = HashSet<String>;
type MHS = HashMap<String, HS>;

impl Base for Work {}
impl TBase for Work {}

impl Work {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, client: &Client) -> Resp {
        let data = para.ckobj()?;

        match cmd {
            "census" => self.do_census(data, client).await,
            "grind" => self.do_grind(data, client).await,
            "batch" => self.do_batch(data, client).await,
            "nodata" => self.do_nodata(data, client).await,
            "lost" => self.do_lost(data, client).await,
            _ => throw_err!(ECode::NoServe, "no work serve"),
        }
    }

    #[instrument(skip_all, name = "do_grind")]
    async fn do_grind(&self, data: &MapSv, client: &Client) -> Resp {
        let opt = data.ck_array("opt")?;

        let cmd = json!({
            "db": "pick",
            "n": "count",
            "obj": "*",
            "opt": {
                "and": opt,
            }
        });

        let body = client.timedb(&cmd, "get").await?;
        let body = body.ck_obj("data")?.ck_obj("count")?.ck_array("data")?;

        let mut data = HashMap::new();

        for d in body.iter() {
            let d = d.ckobj()?;

            let wsn = match d.k_str("wsn") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let num = match d.k_i64("num") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            data.entry(wsn).or_insert(Vec::new()).push(num);
        }

        if data.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        let mut ret = Vec::new();

        for (k, v) in data.iter() {
            let val = match self.calc_num(v) {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            ret.push(json!({
                "wsn": k,
                "num": val,
            }))
        }

        ok!(ret)
    }

    fn calc_num(&self, data: &Vec<i64>) -> Option<i64> {
        let mut it = data.iter();
        let mut first = *it.next()?;
        let mut count = 0;

        for d in it {
            let n = *d;

            if n > first {
                count += n - first;
            }

            first = n;
        }

        Some(count)
    }

    #[instrument(skip_all, name = "do_lost")]
    async fn do_lost(&self, data: &MapSv, client: &Client) -> Resp {
        let line = data.ck_str("line")?;
        let box_sn = data.ck_str("k")?;

        let cmd = json!({ "line": line, "sn": box_sn });

        let body = client
            .cfgdb(&cmd, &format!("file/list/box_{box_sn}"))
            .await?;
        let body = body.ck_array("data")?;

        let mut ret = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;

            let sn = d.ck_str("sn")?;
            if sn == "attr" {
                continue;
            }

            let cmd = json!({ "line": line, "k": sn });

            let psn = client.cfgdb(&cmd, "psn/get").await?;
            let psn = psn.ck_obj("data")?;

            ret.push(json!({
                "sn": sn,
                "standout": psn.k_f64("standout").is_some(),
                "angle": psn.k_f64("angle").is_some(),
                "kia": psn.k_f64("kia").is_some(),
                "inwidth": psn.k_f64("inwidth").is_some(),
                "india": psn.k_f64("india").is_some(),
                "outwidth": psn.k_f64("outwidth").is_some(),
                "outdia": psn.k_f64("outdia").is_some(),
            }));
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_nodata")]
    async fn do_nodata(&self, data: &MapSv, client: &Client) -> Resp {
        let line = data.ck_str("line")?;
        let sn = data.ck_str("sn")?;
        let tech = data.ck_str("tech")?;
        let box_sn = data.ck_str("box")?;
        let pos = data.ck_str("pos")?;

        let cmd = json!({
            "line": line,
            "sn": sn,
            "v": {
                "sn": sn,
                "pos": pos,
            }
        });

        client.cfgdb(&cmd, &format!("file/md/box_{box_sn}")).await?;

        let task_sn = format!("old-{tech}");

        let cmd = json!({ "line": line, "k": task_sn });

        if let Err(_) = client.cfgdb(&cmd, "task/get").await {
            let cmd = json!({
                "line": line,
                "v": {
                    "sn": task_sn,
                    "pstart": base::cur_time("%Y-%m-%d %H:%M:%S"),
                    "pend": self.next_cur_time_day(1, "%Y-%m-%d %H:%M:%S")?,
                    "pnum": 20000,
                    "state": "open",
                    "tech": tech,
                },
            });

            client.cfgdb(&cmd, "task/add").await?;
        }

        let cmd = json!({ "line": line, "k": sn });

        let body_psn = match client.cfgdb(&cmd, "psn/get").await {
            Ok(v) => v,
            Err(_) => {
                let cmd = json!({
                    "line": line,
                    "k": task_sn,
                    "v": [{
                        "sn": sn,
                        "tech": tech,
                        "boxaway": box_sn,
                    }]
                });

                client.cfgdb(&cmd, "psn/add").await?;

                return ok!();
            }
        };

        let body_psn = body_psn.ck_obj("data")?;
        let old_task = body_psn.ck_str("task")?;

        if old_task == task_sn {
            return ok!();
        }

        let cmd = json!({
            "line": line,
            "k": task_sn,
            "v": [sn]
        });

        client.cfgdb(&cmd, "task/movepsn").await?;

        let cmd = json!({
            "line": line,
            "k": old_task,
            "rmpsn": false,
            "v": [sn]
        });

        client.cfgdb(&cmd, "task/rmpsn").await?;

        let cmd = json!({
            "line": line,
            "k": sn,
            "v": {
                "task": task_sn,
                "tech": tech,
                "boxaway": box_sn,
            }
        });

        client.cfgdb(&cmd, "psn/md").await
    }

    #[instrument(skip_all, name = "do_census")]
    async fn do_census(&self, data: &MapSv, client: &Client) -> Resp {
        let opt = data.ck_array("opt")?;

        let tab = vec![
            "outdia",
            "outdt",
            "outwidth",
            "india",
            "indt",
            "inwidth",
            "angle",
            "handangle",
            "standout",
            "handstandout",
            "rotate",
            "pairlog",
            "packlog",
        ];

        // tech: {
        //     "inner": set(psn)
        // }
        let mut real_data = HashMap::<String, MHS>::new();

        warn!("census: get real data");

        for d_tab in tab {
            let db = if d_tab == "pairlog" || d_tab == "packlog" {
                "log"
            } else {
                "realtime"
            };

            let cmd = json!({
                "n": d_tab,
                "db": db,
                "obj": "*",
                "opt": {
                    "and": opt,
                    "desc": true,
                }
            });

            let body = match client.timedb(&cmd, "get").await {
                Ok(v) => v,
                Err(_) => {
                    continue;
                }
            };

            let data = body.ck_obj("data")?.ck_obj(d_tab)?.ck_array("data")?;

            let tab_tmp = if d_tab.ends_with("standout") {
                "standout"
            } else if d_tab.ends_with("angle") {
                "angle"
            } else {
                d_tab
            };

            for d in data.iter() {
                let psn = match d.k_str("psn") {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };
                let tsn = match d.k_str("tsn") {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };

                match real_data.get_mut(tsn) {
                    Some(tab_tp) => match tab_tp.get_mut(tab_tmp) {
                        Some(psn_tmp) => {
                            psn_tmp.insert(psn.to_string());
                        }
                        None => {
                            let mut psn_tmp = HashSet::new();
                            psn_tmp.insert(psn.to_string());

                            tab_tp.insert(tab_tmp.to_string(), psn_tmp);
                        }
                    },
                    None => {
                        let mut psn_tmp = HashSet::new();
                        psn_tmp.insert(psn.to_string());

                        let mut tp_tmp = HashMap::new();
                        tp_tmp.insert(tab_tmp.to_string(), psn_tmp);

                        real_data.insert(tsn.to_string(), tp_tmp);
                    }
                }
            }
        }

        let mut ret = Vec::new();

        warn!("census: get psn data");

        for (tech, data) in real_data.iter() {
            let mut val = HashMap::new();

            val.insert("tech", json!(tech));
            val.insert("tp", json!("hand"));

            for (tab, psn) in data.iter() {
                val.insert(tab, json!(psn.len()));
            }

            ret.push(json!(val));
        }

        let _ = self.census_auto(data, client, &mut ret).await;

        ok!(ret)
    }

    fn ck_auto(&self, n: &str, data: &MapSv, ret: &mut HashMap<String, MapSv>) -> Option<()> {
        let (dia, dt, width) = self.get_val(data)?;
        let psn = data.k_str("psn")?;
        let tsn = data.k_str("tsn")?;

        let pre = if n == "autoin" { "in" } else { "out" };
        let k_dt = format!("{pre}dt");
        let k_dia = format!("{pre}dia");
        let k_width = format!("{pre}width");

        match ret.get_mut(psn) {
            Some(v) => {
                if let None = v.get_mut(&k_dt) {
                    v.insert(k_dt, json!(dt));
                }

                if let None = v.get_mut(&k_dia) {
                    v.insert(k_dia, json!(dia));
                }

                if let None = v.get_mut(&k_width) {
                    v.insert(k_width, json!(width));
                }
            }
            None => {
                let mut tmp = MapSv::new();

                tmp.insert("sn".to_string(), json!(psn));
                tmp.insert("tsn".to_string(), json!(tsn));
                tmp.insert(k_dt, json!(dt));
                tmp.insert(k_dia, json!(dia));
                tmp.insert(k_width, json!(width));

                ret.insert(psn.to_string(), tmp);
            }
        }

        Some(())
    }

    async fn ck_hand(
        &self,
        n: &str,
        tech: &MapSv,
        data: &MapSv,
        ret: &mut HashMap<String, MapSv>,
    ) -> Option<()> {
        let val = data.k_f64("val")?;
        let psn = data.k_str("psn")?;
        let b_val = tech.k_f64(n)?;
        let tsn = tech.k_str("sn")?;

        let val = self.to_m(val - b_val);

        match ret.get_mut(psn) {
            Some(v) => {
                if let None = v.get_mut(n) {
                    v.insert(n.to_string(), json!(val));
                }
            }
            None => {
                let mut tmp = MapSv::new();

                tmp.insert("sn".to_string(), json!(psn));
                tmp.insert("tsn".to_string(), json!(tsn));
                tmp.insert(n.to_string(), json!(val));

                ret.insert(psn.to_string(), tmp);
            }
        }

        Some(())
    }

    #[instrument(skip_all, name = "do_batch")]
    async fn do_batch(&self, data: &MapSv, client: &Client) -> Resp {
        let opt = data.ck_obj("opt")?;
        let bsn = data.ck_str("bsn")?;
        let line = data.ck_str("line")?;
        let tp = data.ck_str("tp")?;

        let tab = if tp == "inner" {
            vec!["india", "indt", "inwidth", "autoin"]
        } else if tp == "outer" {
            vec!["outdia", "outdt", "outwidth", "autout"]
        } else {
            throw_err!(ECode::Para, "tp error");
        };

        let mut real_data = HashMap::<String, MapSv>::new();
        let mut tech_map = HashMap::<String, MapSv>::new();

        warn!("batch: get real data");

        for n in tab {
            let cmd = json!({
                "n": n,
                "obj": "*",
                "opt": opt
            });

            let body = match client.timedb(&cmd, "get").await {
                Ok(v) => v,
                Err(_) => {
                    continue;
                }
            };

            let data = body.ck_obj("data")?.ck_obj(n)?.ck_array("data")?;

            warn!("{} len: {}", n, data.len());

            for d in data.iter() {
                let d = d.ckobj()?;

                let psn = match d.k_str("psn") {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };

                let t: Vec<char> = psn.chars().collect();
                if t.len() < 8 {
                    continue;
                }

                let p = format!("{}{}{}{}{}", t[0], t[1], t[2], t[4], t[6]);
                if p != bsn {
                    continue;
                }

                let tsn = match d.k_str("tsn") {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };

                if n.starts_with("auto") {
                    self.ck_auto(n, d, &mut real_data);
                } else {
                    let tech = match tech_map.get(tsn) {
                        Some(v) => v.clone(),
                        None => {
                            let cmd = json!({ "line": line, "k": tsn });
                            match client.cfgdb(&cmd, "tech/get").await {
                                Ok(v) => {
                                    let v = v.ck_obj("data")?;
                                    tech_map.insert(tsn.to_string(), v.clone());
                                    v.clone()
                                }
                                Err(_) => {
                                    continue;
                                }
                            }
                        }
                    };

                    self.ck_hand(n, &tech, d, &mut real_data).await;
                }
            }
        }

        let mut ret = Vec::new();

        warn!("batch: get psn data");

        for (_, data) in real_data.iter() {
            ret.push(data);
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "census_auto")]
    async fn census_auto(&self, data: &MapSv, client: &Client, ret: &mut Vec<Value>) -> RNull {
        let opt = data.ck_array("opt")?;

        let tab = vec!["autoin", "autout", "autoproduct", "apairlog"];

        // tech: {
        //     "inner": set(psn)
        // }
        let mut real_data = HashMap::<String, MHS>::new();

        warn!("cenauto: get real data");

        for d_tab in tab {
            let db = if d_tab == "apairlog" {
                "log"
            } else {
                "realtime"
            };

            let cmd = json!({
                "n": d_tab,
                "db": db,
                "obj": "*",
                "opt": {
                    "and": opt,
                    "desc": true,
                }
            });

            let body = match client.timedb(&cmd, "get").await {
                Ok(v) => v,
                Err(_) => {
                    continue;
                }
            };

            let data = body.ck_obj("data")?.ck_obj(d_tab)?.ck_array("data")?;

            for d in data.iter() {
                let psn = match d.k_str("psn") {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };
                let tsn = match d.k_str("tsn") {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };

                if d_tab == "autoproduct" {
                    warn!("cenauto: autoproduct data");

                    let val_tp = vec!["standout", "angle", "kia"];

                    for tp_v in val_tp {
                        if let None = d.k_f64(tp_v) {
                            continue;
                        }

                        match real_data.get_mut(tsn) {
                            Some(tab_tp) => match tab_tp.get_mut(tp_v) {
                                Some(psn_tmp) => {
                                    psn_tmp.insert(psn.to_string());
                                }
                                None => {
                                    let mut psn_tmp = HashSet::new();
                                    psn_tmp.insert(psn.to_string());

                                    tab_tp.insert(tp_v.to_string(), psn_tmp);
                                }
                            },
                            None => {
                                let mut psn_tmp = HashSet::new();
                                psn_tmp.insert(psn.to_string());

                                let mut tp_tmp = HashMap::new();
                                tp_tmp.insert(tp_v.to_string(), psn_tmp);

                                real_data.insert(tsn.to_string(), tp_tmp);
                            }
                        }
                    }
                } else {
                    match real_data.get_mut(tsn) {
                        Some(tab_tp) => match tab_tp.get_mut(d_tab) {
                            Some(psn_tmp) => {
                                psn_tmp.insert(psn.to_string());
                            }
                            None => {
                                let mut psn_tmp = HashSet::new();
                                psn_tmp.insert(psn.to_string());

                                tab_tp.insert(d_tab.to_string(), psn_tmp);
                            }
                        },
                        None => {
                            let mut psn_tmp = HashSet::new();
                            psn_tmp.insert(psn.to_string());

                            let mut tp_tmp = HashMap::new();
                            tp_tmp.insert(d_tab.to_string(), psn_tmp);

                            real_data.insert(tsn.to_string(), tp_tmp);
                        }
                    }
                }
            }
        }

        warn!("cenauto: get psn data");

        for (tech, data) in real_data.iter() {
            let mut val = HashMap::new();

            val.insert("tech", json!(tech));
            val.insert("tp", json!("auto"));

            for (tab, psn) in data.iter() {
                val.insert(tab, json!(psn.len()));
            }

            ret.push(json!(val));
        }

        Ok(())
    }
}
